# @Time     : 2025/4/28 11:36
# @Software : Python 3.10
# @About    :
__all__ = ['Tag', 'Heading', 'Heading1', 'Heading2', 'Heading3', 'Heading4', 'Heading5', 'Heading6',
           'Text', 'BoldText', 'ItalicText', 'Texts',
           'IntenseQuote', 'UnorderedList', 'OrderedList',
           'Image', 'Table']

import io
import re
from typing import Iterable, Union, Optional, IO, Iterator, Literal
from urllib.parse import urlparse

import numpy as np
import requests



class Tag:
    _tag: str
    _style: Optional[str] = None


    @property
    def description(self):
        return (f"{self.__class__.__name__}(\n"
                f"\ttag={repr(self._tag)}, \n"
                f"\tstyle={repr(self._style)}\n"
                f")")

    @property
    def tag(self):
        return self._tag

    @property
    def style(self):
        return self._style

    def set_style(self, value):
        if value is not None:
            self._style = value
        return self


class Heading(str, Tag):
    _level: int = 0
    _tag: str = 'title'


    @property
    def description(self):
        return (f"{self.__class__.__name__}(\n"
                f"\ttag={repr(self._tag)}, \n"
                f"\tstyle={repr(self._style)}, \n"
                f"\ttitle={repr(self)}\n"
                f")")

    @property
    def level(self):
        return self._level



class Heading1(Heading):
    _level: int = 1
    _tag: str = 'title_1'



class Heading2(Heading):
    _level: int = 2
    _tag: str = 'title_2'


class Heading3(Heading):
    _level: int = 3
    _tag: str = 'title_3'


class Heading4(Heading):
    _level: int = 4
    _tag: str = 'title_4'


class Heading5(Heading):
    _level: int = 5
    _tag: str = 'title_5'


class Heading6(Heading):
    _level: int = 6
    _tag: str = 'title_6'


class Text(str, Tag):
    _tag: str = 'text'

    @property
    def description(self):
        return (f"{self.__class__.__name__}(\n"
                f"\ttag={repr(self._tag)}, \n"
                f"\tstyle={repr(self._style)}, \n"
                f"\ttext={repr(self)}\n"
                f")")


class BoldText(Text):
    _tag: str = 'bold_text'


class ItalicText(Text):
    _tag: str = 'italic_text'


class Texts(list, Tag):
    _tag: str = 'paragraph'

    def __init__(self, iterable: Iterable[Union[Text, BoldText, ItalicText]] = None):
        if iterable is None:
            iterable = []
        super().__init__(iterable)

    def __str__(self):
        return ''.join(str(item) for item in self)

    @property
    def description(self):
        _texts = ', '.join([''.join([i.strip('\n').strip('\t') for i in i.description.split('\n')]) for i in self])
        return (f"{self.__class__.__name__}(\n"
                f"\ttag={repr(self._tag)}, \n"
                f"\tstyle={repr(self._style)}, \n"
                f"\ttexts=[{_texts}]\n"
                f")")

    @property
    def has_child(self):
        return True

    def __setitem__(self, index, item: Union[Text, BoldText, ItalicText]):
        super().__setitem__(index, item)

    def insert(self, index, item: Union[Text, BoldText, ItalicText]):
        super().insert(index, item)

    def append(self, item: Union[Text, BoldText, ItalicText]):
        if item.style is None:
            item.set_style(self.style)
        super().append(item)

    def extend(self, other: Union[Iterable[Union[Text, BoldText, ItalicText]], 'Texts']):
        if isinstance(other, type(self)):
            super().extend(other)
        else:
            super().extend(str(item) for item in other)


class IntenseQuote(Texts):
    _tag: str = 'quote'


class UnorderedList(list, Tag):
    _tag: str = 'unordered_list'

    def __init__(self, iterable: Iterable[Texts] = None):
        if iterable is None:
            iterable = []
        super().__init__(iterable)

    def __str__(self):
        return '\n'.join(str(item) for item in self)


class OrderedList(list, Tag):
    _tag: str = 'ordered_list'

    def __init__(self, iterable: Iterable[Texts] = None):
        if iterable is None:
            iterable = []
        super().__init__(iterable)

    def __str__(self):
        return '\n'.join(str(item) for item in self)


class Image(Tag):
    _tag: str = 'image'

    def __init__(self, image_path_or_stream: Union[str, IO[bytes]],
                 alt: Optional[str] = None,
                 width: Optional[int] = None,
                 height: Optional[int] = None):
        self._alt = alt
        self._width = width
        self._height = height

        self._image = image_path_or_stream

    @property
    def description(self):
        return (f"{self.__class__.__name__}(\n"
                f"\ttag={repr(self._tag)}, \n"
                f"\tstyle={repr(self._style)}, \n"
                f"\timage={repr(self._image)}\n"
                f")")

    def _is_url(self, path: str):
        try:
            result = urlparse(path)
            return all([result.scheme, result.netloc])
        except ValueError:
            return False

    def read_image(self):
        if isinstance(self._image, str):
            if self._is_url(self._image):
                response = requests.get(self._image)
                if response.status_code == 200:
                    return io.BytesIO(response.content)
                else:
                    raise ValueError(f"Failed to download image from {self._image}. Response is {response.text}")
            else:
                return self._image
        else:
            return self._image

    @property
    def style(self):
        return self._style

    @property
    def image(self):
        return self._image

    @property
    def alt(self):
        return self._alt

    @property
    def width(self):
        return self._width

    @property
    def height(self):
        return self._height


class Cell:
    def __init__(self, text: Union[str, int, float],
                 col: int, row: int,
                 colspan: int = 1, rowspan: int = 1,
                 is_header: bool = False, align: Literal['left', 'right', 'center'] = 'center'):
        self._text = text

        self._col = col
        self._row = row

        assert colspan > 0, "colspan must be greater than 0"
        self._colspan = colspan
        assert rowspan > 0, "rowspan must be greater than 0"
        self._rowspan = rowspan

        self._is_header = is_header
        self._align = align

    def __str__(self):
        return str(self._text)

    def __repr__(self):
        return f"Cell(text={repr(self._text)}, is_header={repr(self._is_header)})"

    @property
    def is_header(self):
        return self._is_header

    @property
    def align(self) -> str:
        return  self._align

    @align.setter
    def align(self, align: Literal['left', 'right', 'center']):
        self._align = align

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, text: Union[str, int, float]):
        self._text = text

    @property
    def colspan(self):
        return self._colspan

    @colspan.setter
    def colspan(self, colspan: int):
        assert colspan > 0, "colspan must be greater than 0"
        self._colspan = colspan

    @property
    def rowspan(self):
        return self._rowspan

    @rowspan.setter
    def rowspan(self, rowspan: int):
        assert rowspan > 0, "rowspan must be greater than 0"
        self._rowspan = rowspan

    @property
    def col(self):
        return self._col

    @property
    def row(self):
        return self._row


class Table(Tag):
    _tag: str = 'table'

    def __init__(self):
        self._map = np.zeros((1, 1), dtype=np.uint8)
        self._values = []

    @property
    def size(self):
        return self._map.shape

    def from_html(self, element):
        self._map = np.zeros((1, 1), dtype=np.uint8)
        self._values = []
        _col, _row = 0, 0  # 列 行
        if trs := element.find_all('tr'):
            for tr in trs:
                is_header = False

                if _row != 0:
                    while self._map.shape[0] < _row + 1:
                        self._map = np.insert(self._map,
                                              self._map.shape[0],
                                              np.zeros(self._map.shape[1], dtype=np.uint),
                                              axis=0)
                    # 扩充行
                    while self._map[_row].sum() == self._map.shape[1]:
                        _row += 1
                        if self._map.shape[0] < _row:
                            self._map = np.insert(self._map,
                                                  self._map.shape[0],
                                                  np.zeros(self._map.shape[1], dtype=np.uint),
                                                  axis=0)

                if tds := tr.find_all('th'):
                    is_header = True
                else:
                    tds = tr.find_all('td')

                for td in tds:
                    text = td.text.strip()

                    rowspan = int(td.get('rowspan', 1))
                    colspan = int(td.get('colspan', 1))

                    if align := re.findall("text-align:\s*(left|center|right)(?:\s*;|)", td.get('style', "")):
                        align = align[0]
                    else:
                        align = 'center'
                    # 补齐列
                    while self._map.shape[1] < (_col + colspan):
                        self._map = np.insert(self._map, self._map.shape[1],
                                              np.zeros(self._map.shape[0], dtype=np.uint8), axis=1)

                    # 补齐行
                    while self._map.shape[0] < _row + rowspan:
                        self._map = np.insert(self._map, self._map.shape[0],
                                              np.zeros(self._map.shape[1], dtype=np.uint8), axis=0)

                    # 跳过已占用单元格
                    while self._map[_row, _col] == 1:
                        _col += 1

                    self._values.append(
                        Cell(text,
                             col=_col, row=_row,
                             colspan=colspan, rowspan=rowspan,
                             is_header=is_header,
                             align=align)
                    )
                    self._map[_row:_row + rowspan, _col:_col + colspan] = 1
                    _col += colspan
                _col = 0
                _row += 1

        return self

    def __iter__(self) -> Iterator[Cell]:
        for cell in self._values:
            yield cell
